3차 프로젝트, ChatGPT를 이용한 챗봇 애플리케이션 - estsoft {Django, DRF}

기본 요구사항

선택 요구사항

Diagrams

기본 요구사항: FE와 BE 분리: 프론트엔드에서 백엔드 서버를 통해 요청을 보내준다.

Flowchart

flowchart LR
	id1[Client]
	id2[FE]
	id3[BE]
	id4[OpenAI]
	id5[DataBase]

	id1 -- 1 --> id2
	id2 -- 2 --> id1
	id1 -- 3 --> id3
	id3 -- 4 --> id4
	id4 -- 4 --> id3
	id3 -- 4 --> id5
	id5 -- 4 --> id3
	id3 -- 5 --> id1
  1. 클라이언트가 프론트에 HTML 문서를 요청함.
  2. 프론트는 JS코드가 담긴 HTML 문서를 반환하고 클라이언트는 문서를 렌더링함.
  3. 클라이언트는 백엔드에게 다음과 같은 요청들을 보낸다.
    1. 회원가입, 로그인, 로그아웃과 같은 사용자 인증/인가
    2. 모든 유저의 채팅내역
    3. 본인의 채팅내역
    4. 챗봇과의 채팅세션
  4. 백엔드는 클라이언트의 요청에 따라서 두 가지 서비스에 요청을 보낸다. 유스케이스 그래프는 따로
    1. DB
    2. OpenAI
  5. 백엔드가 각각의 서비스로부터 응답을 받으면 이를 통해 클라이언트에게 응답을 전송함.

Usecase Diagram

chatgpt-usecase.excalidraw
chatgpt-usecase.excalidraw.png

Main Flow

Error Flow, E-1

Class Diagram

classDiagram 
	Anonymous --|> User
	Member --|> User
	ChatBot "0..*" -- "1" Member
	ChatBot "1" o-- "1..*" Prompt
	ChatBot "1" o-- "1" Config
	ChatBot "1" o-- "1..*" Reply
	Reply "1" -- "1..*" Choice
	
	CurrentState --|> Prompt
	Goal --|> Prompt
	Misc --|> Prompt

ChatBot은 데이터이다. 새 세션을 생성하거나 세션목록을 요청할 때 흩어져 있는 정보를 모아 실제 GPT와 대화가 이루어진 요청과 응답을 고스란히 재현하여야 한다. ChatBot은 컨트롤러이다. 따라서 이름이 적합하지는 않은 것 같다. 클래스 다이어그램에 들어갈 필요도 없을 것이고.

ER Diagram

JSON을 정형 데이터베이스에 저장하는법 {question}

example request

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-3.5-turbo",
    "messages": [
      {
        "role": "system",
        "content": "You are a helpful assistant."
      },
      {
        "role": "user",
        "content": "Hello!"
      }
    ]
  }'

example response

{
  "id": "chatcmpl-123",
  "object": "chat.completion",
  "created": 1677652288,
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "\n\nHello there, how may I assist you today?",
    },
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 9,
    "completion_tokens": 12,
    "total_tokens": 21
  }
}

1차: 높은 확장성

3차 프로젝트, ER-Diagram {1차시도}

2차: 정형 스키마 치환

erDiagram
	Member ||--o{ ChatBot : requests
	ChatBot ||--|{ Prompt : aggregates
	ChatBot ||--|{ ChatBotConfig : aggregates
	ChatBot ||--o{ ChatBotReply : aggregates
	ChatBotReply ||--|{ Choice : has

	Member {
		string nickname
		string email
		int born_year "NULL"
		string job "NULL"
	}

	ChatBot {
		timestamp created_at
	}

	Prompt {
		string prompt
		string answer "NULL"
	}

	ChatBotConfig {
		string model
		decimal temperature
		bool stream
		int max_tokens
	}

	ChatBotReply {
		int prompt_token_usage
		int completion_token_usage
		int total_token_usage
	}

	Choice {
		int reply FK
		string role
		string content
		string finish_reason
		int index
	}

Usecase & Component Layer

usecase_component_diagram.excalidraw
usecase_component_diagram.excalidraw.png

Django + React Full Course

Django + React Full Cource Youtube Playlist 로 가세요

구현 - 토큰으로 사용자 인증/인가 수행

DRF는 Stateless 원칙을 지키기 위해 Token based Authentication을 제공한다. (물론 세션방식도 있긴 함) 나는 is_authenticated만 만족시키면 되기 때문에 뭐 특별히 커스텀 권한을 만들 필요는 없다. 하지만 아직까지 JWT를 사용하여 요청의 유저를 식별하는 방법에 대해서 잘 알지 못하겠다.

우선 DRF에서 인증기능 만들기 {drf}에서 공부한 simple jwt 문서를 더 읽어보는 것으로 출발하자.

Simple JWT package {drf}{rest_framework_simplejwt}

이제 장고에서 로그인을 처리하는 로직은 끝난 것 같다. stateless를 구현하기도 했겠다, 나는 들어오는 요청들에 대하여 JWTAuthentication().authenticate(request)만을 수행하여 그 결과를 가지고 바로 쿼리를 진행하면 되기 때문이다. 그러면 access token, refresh token은 누가 책임지냐고? 그거야 프론트가 알아서 하겠지 😏